#include "drv_flash.h"

#define __HAL_FLASH_TIME_REG_SET(__EPPARA0__,__EPPARA1__,__EPPARA2__,__EPPARA3__,__EPPARA4__)           \
                                                        do {                                            \
                                                            FLASH->TS0  = (__EPPARA0__)&0xFF;           \
                                                            FLASH->TS1  = ((__EPPARA0__)>>16)&0x1FF;    \
                                                            FLASH->TS3  = ((__EPPARA0__)>>8)&0xFF;      \
                                                            FLASH->TS2P = (__EPPARA1__)&0xFF;           \
                                                            FLASH->TPS3 = ((__EPPARA1__)>>16)&0x7FF;    \
                                                            FLASH->PERTPE = (__EPPARA2__)&0x1FFFF;      \
                                                            FLASH->SMERTPE = (__EPPARA3__)&0x1FFFF;     \
                                                            FLASH->PRGTPE = (__EPPARA4__)&0xFFFF;       \
                                                            FLASH->PRETPE = ((__EPPARA4__)>>16)&0x3FFF; \
                                                         } while(0U)

static void Flash_Unlock(void)
{
    if (FLASH_CR_LOCK == READ_BIT(FLASH->CR, FLASH_CR_LOCK)) {
        WRITE_REG(FLASH->KEYR, FLASH_KEY1);
        WRITE_REG(FLASH->KEYR, FLASH_KEY2);
        /* 配置flash擦写时间参数 */
        __HAL_FLASH_TIME_REG_SET((*(__IO uint32_t *)(0x1FFF0F6C)),     \
                                (*(__IO uint32_t *)(0x1FFF0F6C+4)),   \
                                (*(__IO uint32_t *)(0x1FFF0F6C+8)),   \
                                (*(__IO uint32_t *)(0x1FFF0F6C+12)),  \
                                (*(__IO uint32_t *)(0x1FFF0F6C+16)));
    }
}

static void Flash_Lock(void)
{
    SET_BIT(FLASH->CR, FLASH_CR_LOCK);
}

static void Flash_WaitIdle(void)
{
    while (FLASH_SR_BSY == READ_BIT(FLASH->SR, FLASH_SR_BSY)){;}
}

static ErrorStatus Flash_PageErase(unsigned int start_addr, unsigned char page_number)
{
    ErrorStatus result = SUCCESS;
    //1) 检查 BSY 位，确认是否没有正在进行的 Flash 操作
    //2) 向 FLASH_KEYR 寄存器依次写 KEY1,KEY2，解除 FLASH_CR 寄存器保护
    //统一FLASH_Unlock入口，防止芯片跑飞，误操作FLASH
    //3) 置位 FLASH_CR 寄存器的 PER 位
    SET_BIT(FLASH->CR, (FLASH_CR_EOPIE|FLASH_CR_PER));
    unsigned int end_addr = start_addr + (page_number * FLASH_PAGE_SIZE);
    for (;start_addr < end_addr; start_addr += FLASH_PAGE_SIZE) {
        LL_IWDG_ReloadCounter(IWDG);
        //向该 page 写任意数据（必须 32bit 数据）
        HW32_REG(start_addr) = 0xFFFFFFFF;
        //5) 等待 BSY 位被清零
        while (FLASH_SR_BSY == READ_BIT(FLASH->SR, FLASH_SR_BSY));
        //6) 检查 EOP 标志位被置位
        if (FLASH_SR_EOP == READ_BIT(FLASH->SR, FLASH_SR_EOP)) {
            result = SUCCESS;
            //7) 清零 EOP 标志, 写 1，清零该位。
            SET_BIT(FLASH->SR, FLASH_SR_EOP);
        } else {
            result = ERROR;
            break;
        }
    }
    CLEAR_BIT(FLASH->CR, (FLASH_CR_EOPIE|FLASH_CR_PER));
    return result;
}

static ErrorStatus Flash_Write(unsigned int start_addr, unsigned char *data, unsigned int size)
{
    unsigned int offset = 0;
    ErrorStatus result = SUCCESS;

    if ((size&0x7F)||(start_addr&0x7F)) {
        return ERROR;
    }

    //1) 检查 FLASH_SR 寄存器 BSY 位，确认没有正在进行的 flash 操作
    //2) 如果没有正在进行的 flash erase 或者 program 操作，则软件读出该 Page 的 32 个
    //word（如果该 page 已有数据存放，则进行该步骤，否则跳过该步骤）
    //3) 向 FLASH_KEYR 寄存器依次写 KEY1 和 KEY2，解除 FLASH_CR 寄存器的保护
    //统一FLASH_Unlock入口，防止芯片跑飞，误操作FLASH
    //4) 置位 FLASH_CR 寄存器的 PG 位
    SET_BIT(FLASH->CR, (FLASH_CR_EOPIE|FLASH_CR_PG));

    for (unsigned int i = 0; i < size / 0x80; i++) {
        LL_IWDG_ReloadCounter(IWDG);
        for (unsigned char j = 0; j < 0x20; j++) {
            //5) 向目标地址进行第1到第31个word的program操作（只接受32bit的program）
            HW32_REG(start_addr+offset) = HW32_REG(data+offset);
            offset += 4;
            if ((0x20 - 2) == j) {
                //6) 在软件写第 31 个 word 后， 置位 FLASH_CR 寄存器的 PGSTRT
                SET_BIT(FLASH->CR, FLASH_CR_PGSTRT);
            }
        }
        //7) 写第 32 个 word 后
        //8) 等待 FLASH_SR 寄存器的 BSY 位被清零
        while (FLASH_SR_BSY == READ_BIT(FLASH->SR, FLASH_SR_BSY));
        //9) 检查 FLASH_SR 寄存器的 EOP 标志位（当 program 操作已经成功，该位被置位），然后软件清零该位
        if (FLASH_SR_EOP == READ_BIT(FLASH->SR, FLASH_SR_EOP)) {
            result = SUCCESS;
            //清零 EOP 标志, 写 1，清零该位。
            SET_BIT(FLASH->SR, FLASH_SR_EOP);
        } else {
            result = ERROR;
            break;
        }
    }

    //10) 如果不再有 program 操作，则软件清除 PG 位
    CLEAR_BIT(FLASH->CR, (FLASH_CR_EOPIE|FLASH_CR_PG));
    return result;
}

static void Flash_Read(unsigned int start_addr, unsigned char *data, unsigned int size)
{
    signed int remain = 0;
    remain = size - 4;
    while (remain >= 0) {
        LL_IWDG_ReloadCounter(IWDG);
        HW32_REG(data) = HW32_REG(start_addr);
        data += 4;
        start_addr += 4;
        size -= 4;
        remain = size - 4;
    }

    if (size > 0) {
        for (unsigned int i = 0; i < size; i++) {
            unsigned int temp = HW32_REG(start_addr);
            *data = (unsigned char)(temp >> (i * 8));
            data++;
        }
    }
}

int drv_flash_write(unsigned int addr, unsigned char *data, unsigned int size)
{
    unsigned int waddr = addr;
    unsigned char page_num = size / FLASH_PAGE_SIZE;
    int result = APP_SUCCESS;
    if (size & 0x7F) {
        page_num += 1;
    }
    Flash_WaitIdle();
    Flash_Unlock();
    if (SUCCESS != Flash_PageErase(waddr, page_num)) {
        result = APP_FAILURE;
    } else {
        if (SUCCESS != Flash_Write(waddr, data, size)) {
            result = APP_FAILURE;
        }
    }
    Flash_Lock();
    return APP_SUCCESS;
}

void drv_flash_read(unsigned int addr, unsigned char *data, unsigned int size)
{
    unsigned int raddr = addr;
    Flash_Read(raddr, data, size);
}

void drv_flash_erase(unsigned int addr, unsigned int size)
{
    unsigned int waddr = addr;
    unsigned char page_num = size / FLASH_PAGE_SIZE;
    if (size & 0x7F) {
        page_num += 1;
    }
    Flash_WaitIdle();
    Flash_Unlock();
    Flash_PageErase(waddr, page_num);
    Flash_Lock();
}